package com.sina.shop.web.resolver;

import com.sina.shop.core.exception.ShopCustomException;
import com.sina.shop.util.BindingUtils;
import com.sina.shop.util.ExceptionUtils;
import com.sina.shop.util.ResultCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.core.Ordered;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.Assert;
import org.springframework.validation.BindException;
import org.springframework.web.HttpMediaTypeException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.*;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.View;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import static com.sina.shop.util.ExceptionUtils.getSimpleMessage;


/**
 * 用于在捕捉 controller 未处理的异常
 * created  on 11/05/2017.
 */
public class JsonViewExceptionResolver implements HandlerExceptionResolver, Ordered, InitializingBean {

    private static final Logger LOG = LoggerFactory.getLogger(JsonViewExceptionResolver.class);

    private MessageSource messageSource;

    private View defaultView;


    @Override
    public int getOrder() {
        return 0;
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(messageSource, "messageSource must not null");
    }

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response
            , Object handler, Exception e) {
        Map<String, Object> result;
        ModelAndView mv = new ModelAndView(getDefaultView());
        if (e instanceof ShopCustomException) {
            result = new HashMap<>(6);
            ResultCode resultCode = ((ShopCustomException) e).getResultCode();
            if (resultCode == null) {
                resultCode = ResultCode.unknown;
            }
            result.put("code", resultCode);
            result.put("msg", e.getMessage() == null ? resultCode : e.getMessage());

        } else {
            // spring 标准异常
            result = processSpringStandardException(request, response, handler, e);

            if (result == null) {
                // 代码 bug 导致的异常
                result = processInternalServerException(request, response, e);
            }
        }

        /*if (e instanceof IViewName) {
            String viewName = ((IViewName) e).getViewName();
            if (viewName != null) {
                mv.setViewName(viewName);
            }

        }*/

        // 如果无 data 键则加入
        result.putIfAbsent("data", Collections.emptyMap());

        mv.addObject("result", result);
        return mv;
    }


    /**
     * 程序未期待的异常,由 bug 引起
     */
    protected Map<String, Object> processInternalServerException(HttpServletRequest request, HttpServletResponse response
            , Exception e) {
        LOG.error("internal server exception ", e);
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        Map<String, Object> result = new HashMap<>(6);
        result.put("code", ResultCode.unknown);
        result.put("msg", e.getMessage());
        return result;
    }

    /**
     * 处理 spring 标准异常，相应的 http 状会被设置
     *
     * @return 返回 null 如果不是要求处理的异常
     */
    protected Map<String, Object> processSpringStandardException(HttpServletRequest request, HttpServletResponse response
            , Object handler, Exception e) {


        Map<String, Object> result = null;

        if (e instanceof NoHandlerFoundException) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            result = new HashMap<>(6);
            result.put("code", ResultCode.noHandler);
            result.put("msg", e.getMessage());

        } else if (e instanceof BindException) {

            response.setStatus(HttpServletResponse.SC_OK);
            result = new HashMap<>(6);
            result.put("code", ResultCode.dataValidError);
            result.put("msg", "validate failure,see detail in data");
            result.put("data",
                    BindingUtils.convertErrorToMap(((BindException) e).getBindingResult(), messageSource)
            );

        } else if (e instanceof MethodArgumentNotValidException) {
            response.setStatus(HttpServletResponse.SC_OK);
            result = new HashMap<>(6);
            result.put("code", ResultCode.dataValidError);
            result.put("msg", "validate failure,see detail in data");
            result.put("data",
                    BindingUtils.convertErrorToMap(((MethodArgumentNotValidException) e)
                            .getBindingResult(), messageSource)
            );

        } else if (e instanceof TypeMismatchException) {

            result = new HashMap<>(6);
            result.put("code", ResultCode.paramConvertError);
            result.put("msg", e.getMessage());

        } else if (e instanceof HttpMediaTypeException) {

            result = processHttpMediaTypeException(request, response, handler, (HttpMediaTypeException) e);

        } else if (e instanceof HttpMessageConversionException) {

            result = processHttpMessageConversionException(request, response, handler, (HttpMessageConversionException) e);

        } else if (e instanceof HttpRequestMethodNotSupportedException) {

            response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);

            result = new HashMap<>(6);
            result.put("code", ResultCode.methodNotAllow);
            result.put("msg", e.getMessage());

        } else if (e instanceof ServletRequestBindingException) {

            result = processServletRequestBindingException(request, response, handler, (ServletRequestBindingException) e);

        } else if (e instanceof MissingServletRequestPartException) {

            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);

            result = new HashMap<>(6);
            result.put("code", ResultCode.notMultipart);
            result.put("msg", e.getMessage());

        }
        return result;
    }


    protected Map<String, Object> processHttpMediaTypeException(HttpServletRequest request, HttpServletResponse response
            , Object handler, HttpMediaTypeException e) {
        Throwable root = ExceptionUtils.getRootException(e);
        Map<String, Object> result = null;

        if (e instanceof HttpMediaTypeNotSupportedException) {

            response.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
            result = new HashMap<>(6);
            result.put("code", ResultCode.mediaNotSupport);
            result.put("msg", root.getMessage());

        } else if (e instanceof HttpMediaTypeNotAcceptableException) {

            response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
            result = new HashMap<>(6);
            result.put("code", ResultCode.mediaNotAcceptable);
            result.put("msg", root.getMessage());
        }
        return result;
    }

    /**
     * 处理 HttpMessageConversionException 及其子异常
     *
     * @return 格式如 {code:33,msg:""} 的 map,若非 标准异常(spring 定义的异常) 则返回 null
     */
    @SuppressWarnings("")
    protected Map<String, Object> processHttpMessageConversionException(HttpServletRequest request
            , HttpServletResponse response, Object handler, HttpMessageConversionException e) {

        Map<String, Object> result = null;

        if (e instanceof HttpMessageNotReadableException) {

            result = new HashMap<>(6);
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            result.put("code", ResultCode.requestBodyError);
            result.put("msg", getSimpleMessage(e));

        } else if (e instanceof HttpMessageNotWritableException) {
            Throwable root = ExceptionUtils.getRootException(e);
            result = new HashMap<>(6);
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

            result.put("code", ResultCode.responseBodyError);
            result.put("msg", root.getMessage());

        }
        return result;
    }


    /**
     * 处理 ServletRequestBindingException 及其子异常
     *
     * @return 格式如 {code:33,msg:""} 的 map ,若非 标准异常 则返回 null
     */
    protected Map<String, Object> processServletRequestBindingException(HttpServletRequest request
            , HttpServletResponse response, Object handler, ServletRequestBindingException e) {
        Throwable root = ExceptionUtils.getRootException(e);
        Map<String, Object> result = new HashMap<>(6);

        if (e instanceof MissingPathVariableException) {

            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);

            result.put("code", ResultCode.pathVariableNotExtract);
            result.put("msg", String.format("path variable[%s] not extract."
                    , ((MissingPathVariableException) e).getVariableName()));

        } else if (e instanceof MissingServletRequestParameterException) {
            MissingServletRequestParameterException ex = (MissingServletRequestParameterException) e;
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);

            result.put("code", ResultCode.dataValidError);
            result.put("msg", String.format("missing param %s ,it's type is %s"
                    , ex.getParameterName(), ex.getParameterType()));

        } else if (e instanceof UnsatisfiedServletRequestParameterException) {

            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            result.put("code", ResultCode.unsatisfiedParam);
            result.put("msg", e.getMessage());

        } else {
            //ServletRequestBindingException 本身
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            result.put("code", ResultCode.requestBindError);
            result.put("msg", root.getMessage());
        }
        return result;
    }

    public MessageSource getMessageSource() {
        return messageSource;
    }

    public void setMessageSource(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    public View getDefaultView() {
        return defaultView;
    }

    public void setDefaultView(View defaultView) {
        this.defaultView = defaultView;
    }
}
